﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace LanqiQAwebapi.Filter
{
    public class PreventDuplicateRequestsActionFilter : IAsyncActionFilter
    {
        public string[] FactorNames { get; set; }
        public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }

        private readonly IDatabase _database;
        private readonly ILogger<PreventDuplicateRequestsActionFilter> _logger;

        public PreventDuplicateRequestsActionFilter(IConnectionMultiplexer redis, ILogger<PreventDuplicateRequestsActionFilter> logger)
        {
            _database = redis.GetDatabase();
            _logger = logger;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var factorValues = new string?[FactorNames.Length];

            var isFromBody =
                context.ActionDescriptor.Parameters.Any(r => r.BindingInfo?.BindingSource == BindingSource.Body);
            if (isFromBody)
            {
                var parameterValue = context.ActionArguments.FirstOrDefault().Value;
                factorValues = FactorNames.Select(name =>
                    parameterValue?.GetType().GetProperty(name)?.GetValue(parameterValue)?.ToString()).ToArray();
            }
            else
            {
                for (var index = 0; index < FactorNames.Length; index++)
                {
                    if (context.ActionArguments.TryGetValue(FactorNames[index], out var factorValue))
                    {
                        factorValues[index] = factorValue?.ToString();
                    }
                }
            }

            if (factorValues.All(string.IsNullOrEmpty))
            {
                _logger.LogWarning("Please config FactorNames.");

                await next();
                return;
            }

            var idempotentKey = $"{context.HttpContext.Request.Path.Value}:{string.Join("-", factorValues)}";
            var success = await _database.StringSetAsync(idempotentKey, DateTimeOffset.UtcNow.ToString(), AbsoluteExpirationRelativeToNow, When.NotExists);
            if (success)
            {
                await next();
            }
            else
            {
                _logger.LogWarning("Received duplicate request({}), short-circuiting...", idempotentKey);
                context.Result = new AcceptedResult();
            }
        }
    }
}
